/*
 * To change this template, choose Tools | Templates and open the template in the editor.
 */

package com.jellyfishumbrella.GL;

import static org.lwjgl.opengl.GL11.GL_COLOR_ARRAY;
import static org.lwjgl.opengl.GL11.GL_FILL;
import static org.lwjgl.opengl.GL11.GL_FLOAT;
import static org.lwjgl.opengl.GL11.GL_FRONT_AND_BACK;
import static org.lwjgl.opengl.GL11.GL_LINES;
import static org.lwjgl.opengl.GL11.GL_NORMAL_ARRAY;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_2D;
import static org.lwjgl.opengl.GL11.GL_TEXTURE_COORD_ARRAY;
import static org.lwjgl.opengl.GL11.GL_VERTEX_ARRAY;
import static org.lwjgl.opengl.GL11.glBindTexture;
import static org.lwjgl.opengl.GL11.glColorPointer;
import static org.lwjgl.opengl.GL11.glDisableClientState;
import static org.lwjgl.opengl.GL11.glDrawElements;
import static org.lwjgl.opengl.GL11.glEnable;
import static org.lwjgl.opengl.GL11.glEnableClientState;
import static org.lwjgl.opengl.GL11.glNormalPointer;
import static org.lwjgl.opengl.GL11.glPolygonMode;
import static org.lwjgl.opengl.GL11.glPopMatrix;
import static org.lwjgl.opengl.GL11.glPushMatrix;
import static org.lwjgl.opengl.GL11.glRotatef;
import static org.lwjgl.opengl.GL11.glScalef;
import static org.lwjgl.opengl.GL11.glTexCoordPointer;
import static org.lwjgl.opengl.GL11.glTranslatef;
import static org.lwjgl.opengl.GL11.glVertexPointer;
import static org.lwjgl.opengl.GL13.GL_TEXTURE0;
import static org.lwjgl.opengl.GL13.GL_TEXTURE1;
import static org.lwjgl.opengl.GL13.glActiveTexture;
import static org.lwjgl.opengl.GL15.GL_ARRAY_BUFFER;
import static org.lwjgl.opengl.GL15.GL_STATIC_DRAW;
import static org.lwjgl.opengl.GL15.glBindBuffer;
import static org.lwjgl.opengl.GL15.glBufferData;
import static org.lwjgl.opengl.GL15.glGenBuffers;
import static org.lwjgl.opengl.GL30.glBindVertexArray;
import static org.lwjgl.opengl.GL30.glGenVertexArrays;
import static org.lwjgl.opengl.GL31.GL_PRIMITIVE_RESTART;
import static org.lwjgl.opengl.GL31.glPrimitiveRestartIndex;

import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.util.vector.Vector3f;

import com.jellyfishumbrella.util.Trace;

/**
 * @author Rich Helvey Keep all the openGL stuff in here. Pass floatBuffers(or whatever) into the methods from any sub-classes who will be responsible for holding the client-side data themselves
 */
public class MeshGL extends AnimObject implements Renderable, Cloneable {
	public int								VAO_Index;
	public int								NUM_ELEMENTS;

	protected int							NUM_VBOS			= 1;
	protected LinkedHashMap<String, Vertex>	rawVerts;

	protected int							vertexVBO_Index;

	protected FloatBuffer					vertexData			= null;																// Use one VBO for all vertices, but a separate index order for each surface
	protected Map<Surface, IntBuffer>		surfaceVertexOrder	= Collections.synchronizedMap(new HashMap<Surface, IntBuffer>());
	protected int							stride				= 3 + 3 + 4 + 2;														// 3 for Pos, 3 for normal, 4 for color, 2 for uv
	protected int							strideBytes			= stride * 4;															// 4 for bytes

	public Set<UberPoly>					uberPolys			= Collections.synchronizedSet(new HashSet<UberPoly>());
	protected Map<Surface, PolygonGroup>	surfacePolyGroups	= Collections.synchronizedMap(new HashMap<Surface, PolygonGroup>());

	public int								glMode				= GL_LINES;

	public boolean							refreshVerts		= false;
	public boolean							refreshIndices		= false;
	public boolean							reUploadVerts		= false;

	public Vector3d							ltf					= new Vector3d();
	public Vector3d							rbb					= new Vector3d();

	public MeshGL() {
		super();
		clear();
	}

	public void clear() {
		synchronized (surfaceVertexOrder) {
			// synchronized (rawVerts) {
			// textures = new ArrayList<Texture>();

			clearRawVerts();
			// rawVertOrder = new ArrayList<Integer>();
			// polyGroups = Collections.synchronizedSet(new HashSet<PolygonGroup>());
			// defaultPolyGroup = new PolygonGroup();
			// polyGroups.add(defaultPolyGroup);
			// polygons = new HashSet<Polygon>();
			// }

			vertexData = null;
			surfaceVertexOrder = Collections.synchronizedMap(new HashMap<Surface, IntBuffer>());
			// surfaceVertexVBO_Index = Collections.synchronizedMap(new HashMap<Surface, Integer>());
		}
	}

	public void initGPU() {
		setupVAO();
		createVBO();
		uploadVBO();
	}

	public void setupVAO() {
		VAO_Index = glGenVertexArrays();
		glBindVertexArray(VAO_Index);
	}

	private void createVBO() {
		vertexVBO_Index = glGenBuffers();
	}

	public void uploadVBO() {
		glBindBuffer(GL_ARRAY_BUFFER, vertexVBO_Index);
		glBufferData(GL_ARRAY_BUFFER, vertexData, GL_STATIC_DRAW);
		// vertexData = null;
	}

	public void adjustBounds(Vector3d p) {
		ltf.x = Math.min(ltf.x, p.x);
		ltf.y = Math.min(ltf.y, p.y);
		ltf.z = Math.min(ltf.z, p.z);

		rbb.x = Math.max(rbb.x, p.x);
		rbb.y = Math.max(rbb.y, p.y);
		rbb.z = Math.max(rbb.z, p.z);
	}

	public Surface addSurface(String surfaceName) {
		Surface currSurface = RendererGL.addSurface(surfaceName);
		synchronized (surfaceVertexOrder) {
			if (!surfaceVertexOrder.containsKey(currSurface)) {
				surfaceVertexOrder.put(currSurface, null);
				PolygonGroup newGroup = new PolygonGroup();
				newGroup.name = surfaceName;
				surfacePolyGroups.put(currSurface, newGroup);
			}
		}
		return currSurface;
	}

	public int addPoint(Vector3d point) {
		Vertex p = new Vertex(point);
		return addPoint(p);
	}

	public int addPoint(Vertex point) {
		if (!containsRawVert(point)) {
			addRawVert(point);
			int newIndex = getRawVerts().size() - 1;
			point.setIndex(newIndex);
		} else {
			point = getRawVert(point.toString());
		}
		adjustBounds(point.coord);
		point.useCount++;
		return point.getIndex();
	}

	public void addSinglePointPolygon(String polySurfaceName, UberPoly uberPoly, Vertex point) {
		Surface polySurface = addSurface(polySurfaceName);
		PolygonGroup polyGroup = surfacePolyGroups.get(polySurface);
		// synchronized (rawVertOrder) {
		int index = addPoint(point);
		polyGroup.rawVertOrder.add(index);
		polyGroup.rawVertOrder.add(Integer.MAX_VALUE);
		// }
	}

	public void addLine(String polySurfaceName, UberPoly uberPoly, Vertex pointA, Vertex pointB) {
		Surface polySurface = addSurface(polySurfaceName);
		PolygonGroup polyGroup = surfacePolyGroups.get(polySurface);
		// synchronized (rawVertOrder) {
		int indexA = addPoint(pointA);
		int indexB = addPoint(pointB);
		polyGroup.rawVertOrder.add(indexA);
		polyGroup.rawVertOrder.add(indexB);
		polyGroup.rawVertOrder.add(Integer.MAX_VALUE);
		// }
	}

	public Polygon addPolygon(String polySurfaceName, UberPoly uberPoly, ArrayList<Vertex> points) {
		Polygon newPoly = new Polygon(this, points);

		return addPolygon(polySurfaceName, uberPoly, newPoly, false); // we already calculate the
		// polynormal when
		// it's created from a series of points

		// return newPoly;
	}

	public Polygon addPolygon(String polySurfaceName, UberPoly uberPoly, ArrayList<Vertex> points, boolean calcPolyNormal) {

		Polygon newPoly = new Polygon(this, points);

		return addPolygon(polySurfaceName, uberPoly, newPoly, calcPolyNormal);

	}

	public synchronized Polygon addPolygon(String polySurfaceName, UberPoly uberPoly, Polygon poly, boolean calcPolyNormal) {
		Surface polySurface = addSurface(polySurfaceName);
		poly.surface = polySurface;
		PolygonGroup surfacePolyGroup = surfacePolyGroups.get(polySurface);

		synchronized (surfacePolyGroup.rawVertOrder) {
			// synchronized (rawVertOrder) {

			if (calcPolyNormal) {
				poly.calcPolyNormal();
				double dotProduct = poly.normal.x * 0.0 + poly.normal.y * 1.0 + poly.normal.z * 0.0;

				if (dotProduct < 0) {
					Collections.reverse(poly.verts);
					poly.calcPolyNormal();
				}
			}

			for (Vertex currVert : poly.verts) {
				currVert.normal = poly.normal;
				if (currVert.getIndex() == -1) {
					System.out.println("stop");
				}
				System.out.println("PREIndex: " + currVert.toString());
				int index = addPoint(currVert);
				if (index == -1) {
					System.out.println("stop");
				}
				System.out.println("POSTIndex: " + currVert.toString());
				poly.surfacePolyGroupsRawVertOrderIndexStart = Math.min(poly.surfacePolyGroupsRawVertOrderIndexStart, index);
				surfacePolyGroups.get(polySurface).rawVertOrder.add(index);
				currVert.connectedUberPolys.add(uberPoly);
				if (currVert.vertexLODType == UberPoly.VertexLODTag.UNDEFINED) {
					currVert.vertexLODType = UberPoly.VertexLODTag.OUTER;
				}
			}
			surfacePolyGroup.rawVertOrder.add(Integer.MAX_VALUE);
			surfacePolyGroup.polygons.add(poly);
		}
		uberPoly.polygons.add(poly);
		uberPoly.surface = RendererGL.surfaceHash.get(polySurfaceName);
		poly.uberPoly = uberPoly;

		return poly;
	}

	public Surface setPolySurface(Polygon currPoly, String surfaceName) {
		Trace.getCurrentMethodName(currPoly.getIndex() + "  " + surfaceName);
		Surface currSurface = addSurface(surfaceName);

		if (currPoly.surface != null) {
			Surface polySurface = currPoly.surface;
// for (Surface currSurface2 : surfacePolyGroups.keySet()) {
			PolygonGroup surfacePolyGroup = surfacePolyGroups.get(polySurface);
			if (surfacePolyGroup.polygons.contains(currPoly)) {
				surfacePolyGroup.polygons.remove(currPoly);
				//surfacePolyGroup.rebuild();
				surfacePolyGroup.setDirty(true);

				// break;
			}
			// }
		}

		PolygonGroup surfacePolyGroup = surfacePolyGroups.get(currSurface);
		surfacePolyGroup.polygons.add(currPoly); // not sure exactly why this works, unless the surface isn't getting switched properly

		surfacePolyGroup.setDirty(true);
		
		currPoly.surface = currSurface;
		//surfacePolyGroup.rebuild();

		return currSurface;
	}
	
	public void rebuildSurfaces(){
		for (Surface currSurface : surfacePolyGroups.keySet()) {
			PolygonGroup surfacePolyGroup = surfacePolyGroups.get(currSurface);
			if(surfacePolyGroup.isDirty()){
				surfacePolyGroup.rebuild();
			}
			surfacePolyGroup.setDirty(false);
		}
	}

	public Polygon addPolygonFan(String polySurfaceName, UberPoly uberPoly, ArrayList<Vertex> points, boolean calcPolyNormal) {
		Vertex center = new Vertex();

		for (Vertex currVert : points) {
			center.add(currVert);
			center.normal.add(currVert.normal);
			center.glColor.add(currVert.glColor);
			center.uv.add(currVert.uv);
		}
		center.scale(1.0 / points.size());
		center.normal.scale(1.0 / points.size());
		center.glColor.scale(1.0 / points.size());
		center.uv.scale(1.0 / points.size());

		points.add(0, center);
		points.add(points.get(1));

		Polygon poly = addPolygon(polySurfaceName, uberPoly, points, calcPolyNormal);
		center.vertexLODType = UberPoly.VertexLODTag.CENTER;
		poly.isPolygonFan = true;

		// polygroup.polygons.add(poly);
		return poly;
	}

	public LinkedHashSet<Polygon> addBufferedPolygonFan(String polySurfaceName, UberPoly uberPoly, ArrayList<Vertex> points, double rimRatio, boolean calcPolyNormal) {// insets the original fan, and puts a little rim around it
		LinkedHashSet<Polygon> glPolys = new LinkedHashSet<Polygon>();
		ArrayList<Vertex> inset_points = new ArrayList<Vertex>();
		Vertex center = new Vertex();

		for (Vertex currVert : points) {
			center.add(currVert);
			center.normal.add(currVert.normal);
			center.glColor.add(currVert.glColor);
			center.uv.add(currVert.uv);
		}
		center.scale(1.0 / points.size());
		center.normal.scale(1.0 / points.size());
		center.glColor.scale(1.0 / points.size());
		center.uv.scale(1.0 / points.size());
		center.vertexLODType = UberPoly.VertexLODTag.CENTER;

		for (Vertex currVert : points) {
			Vertex newVert = currVert.clone();
			newVert.sub(center);
			newVert.scale(rimRatio);
			newVert.add(center);

			newVert.uv.sub(center.uv);
			newVert.uv.scale(rimRatio);
			newVert.uv.add(center.uv);

			newVert.vertexLODType = UberPoly.VertexLODTag.INNER;

			inset_points.add(newVert);
			if(newVert.getIndex() == -1){
				System.out.println("arg " + newVert.toString());
			}
		}

		for (Vertex currVert : inset_points) {
			currVert.setY(center.y() + (currVert.y() - center.y()) * 0.95);
		}

		synchronized (surfacePolyGroups) {// .get(polySurface).polygons) {
			for (int i = 0; i < points.size(); i++) {
				Vertex centerRim = new Vertex();
				ArrayList<Vertex> rim_points = new ArrayList<Vertex>();
				int i2 = (i + 1) % points.size();
				rim_points.add(points.get(i));
				rim_points.add(points.get(i2));
				rim_points.add(inset_points.get(i2));
				rim_points.add(inset_points.get(i));
				/*
				 * centerRim.add(points.get(i)); centerRim.add(points.get(i2)); centerRim.add(inset_points.get(i2)); centerRim.add(inset_points.get(i)); centerRim.scale(0.25); centerRim.uv.add(points.get(i).uv); centerRim.uv.add(points.get(i2).uv); centerRim.uv.add(inset_points.get(i2).uv); centerRim.uv.add(inset_points.get(i).uv); centerRim.uv.scale(0.25); rim_points.add(0, centerRim); rim_points.add(rim_points.get(1)); centerRim.vertexLODType = UberPoly.VertexLODType.OUTER;
				 */

				Polygon poly = addPolygon(polySurfaceName, uberPoly, rim_points, calcPolyNormal);
				poly.isPolygonFan = true;
				surfacePolyGroups.get(poly.surface).polygons.add(poly);
				glPolys.add(poly);
				

				System.out.println(poly.toString());
			}

			inset_points.add(0, center);
			inset_points.add(inset_points.get(1));

			Polygon poly = addPolygon(polySurfaceName, uberPoly, inset_points, calcPolyNormal);
			poly.isPolygonFan = true;

			PolygonGroup currGroup = surfacePolyGroups.get(poly.surface);
			
			currGroup.polygons.add(poly);
			glPolys.add(poly);
			

			System.out.println(poly.toString());
		}
		
		for(Polygon currPoly : glPolys){
			System.out.println(currPoly.toString());
		}
		
		return glPolys;
	}

	public LinkedHashSet<Polygon> extrude(Polygon poly, Vector3d extrudeVector) {
		LinkedHashSet<Polygon> edgePolys = new LinkedHashSet<Polygon>();
		ArrayList<Vertex> points = new ArrayList<Vertex>();

		Vertex lastNewPoint = null;
		for (Vertex currVert : poly.verts) {
			if (currVert.vertexLODType == UberPoly.VertexLODTag.OUTER) {
				Vector3d pos = new Vector3d();
				pos.x = currVert.x();
				pos.y = currVert.y();
				pos.z = currVert.z();

				Vertex extruded = new Vertex();
				extruded.setX(pos.x + extrudeVector.x);
				extruded.setY(pos.y + extrudeVector.y);
				extruded.setZ(pos.z + extrudeVector.z);
				extruded.vertexLODType = UberPoly.VertexLODTag.BASE;

				if (lastNewPoint != null) {
					points.add(extruded);
					points.add(currVert);
					edgePolys.add(addPolygonFan(poly.uberPoly.surface.name, poly.uberPoly, points, true));
					points.clear();
				} else {
					points.add(currVert);
					points.add(extruded);
				}
				lastNewPoint = extruded;
			}
		}
		return edgePolys;
	}

	public synchronized Collection<? extends Polygon> extrude(UberPoly uberPoly, Vector3d extrudeVector) {
		LinkedHashSet<Polygon> edgePolys = new LinkedHashSet<Polygon>();

		Set tempPolys = Collections.synchronizedSet(new LinkedHashSet<Polygon>());
		tempPolys.addAll(uberPoly.polygons);

		Iterator itr = tempPolys.iterator();
		while (itr.hasNext()) {
			Polygon currPoly = (Polygon) itr.next();
			edgePolys.addAll(extrude(currPoly, extrudeVector));
		}

		return edgePolys;
	}

	/*
	 * public void removePoint(Vertex point) { if (containsRawVert(point)) { point.useCount--; if (point.useCount <= 0) { removeRawVert(point); adjustBounds(point); } } } public void removePoly(Surface polySurface, UberPoly uberPoly, Polygon poly) { PolygonGroup surfacePolyGroup = surfacePolyGroups.get(polySurface); surfacePolyGroup.remove(poly); for (Vertex currVert : poly.verts) { surfacePolyGroups.get(polySurface).rawVertOrder.remove(currVert.index); } }
	 */

	public void removeUberPoly(UberPoly removeUberPoly) {
		Surface surface = removeUberPoly.surface;

		PolygonGroup surfacePolyGroup = surfacePolyGroups.get(surface);

		surfacePolyGroup.remove(removeUberPoly);

		surfacePolyGroup.rebuild();
	}

	public Normal calcPolyNormal(Polygon poly) {
		Normal n = new Normal();
		for (int i = 0; i < poly.verts.size() - 1; i++) {
			Vertex v = poly.verts.get(i);
			Vertex v2 = poly.verts.get(i + 1);
			n.x += (v.y() - v2.y()) * (v.z() + v2.z());
			n.y += (v.z() - v2.z()) * (v.x() + v2.x());
			n.z += (v.x() - v2.x()) * (v.y() + v2.y());
		}
		n.scale(1 / (double) poly.verts.size());
		n.normalize();
		return n;
	}

	public void setRefreshFlags(boolean refreshVerts, boolean refreshIndices) {
		this.refreshVerts = refreshVerts;
		this.refreshIndices = refreshIndices;
	}

	public void finalize() {
		initGPU();
	}

	private synchronized void finalizeVerts() {
		vertexData = BufferUtils.createFloatBuffer(sizeRawVerts() * (stride));
		uploadVerts();
	}

	private synchronized void uploadVerts() {
		Collection<Vertex> c = rawVerts.values();
		Iterator itr = c.iterator();
		int indexStart = 0;
		while (itr.hasNext()) {
			Vertex currVert = (Vertex) itr.next();
			Vector3f currPoint = currVert.toGL();
			Normal currNormal = currVert.normal;
			GLColor currColor = currVert.glColor;
			Vector2d currUV = currVert.uv;

			vertexData.put(currPoint.x);
			vertexData.put(currPoint.y);
			vertexData.put(currPoint.z);
			vertexData.put(currNormal.toGL().x);
			vertexData.put(currNormal.toGL().y);
			vertexData.put(currNormal.toGL().z);
			vertexData.put(currColor.r());
			vertexData.put(currColor.g());
			vertexData.put(currColor.b());
			vertexData.put(currColor.a());
			vertexData.put(currUV.toGL().x);
			vertexData.put(currUV.toGL().y);

			currVert.VBOIndexStart = indexStart++;
		}

		vertexData.flip();
	}

	public void adjVertexVBO(Vertex currVert) {
		int i = currVert.VBOIndexStart;
		if (i != -1) {
			Vector3f currPoint = currVert.toGL();
			Normal currNormal = currVert.normal;
			GLColor currColor = currVert.glColor;
			Vector2d currUV = currVert.uv;

			vertexData.put(i++, currPoint.x);
			vertexData.put(i++, currPoint.y);
			vertexData.put(i++, currPoint.z);
			vertexData.put(i++, currNormal.toGL().x);
			vertexData.put(i++, currNormal.toGL().y);
			vertexData.put(i++, currNormal.toGL().z);
			vertexData.put(i++, currColor.r());
			vertexData.put(i++, currColor.g());
			vertexData.put(i++, currColor.b());
			vertexData.put(i++, currColor.a());
			vertexData.put(i++, currUV.toGL().x);
			vertexData.put(i++, currUV.toGL().y);
		}
	}

	private void finalizeIndices() {
		Trace.getCurrentMethodName();
		synchronized (surfaceVertexOrder) {
			for (Map.Entry<Surface, IntBuffer> entry : surfaceVertexOrder.entrySet()) {
				Surface currSurface = entry.getKey();

				//Trace.log(currSurface.name);

				PolygonGroup currPolyGroup = surfacePolyGroups.get(currSurface);

				int rawVertOrderCount = currPolyGroup.rawVertOrder.size();

				IntBuffer vboIndex = BufferUtils.createIntBuffer(rawVertOrderCount * (stride));

				int i = 0;
				Object[] tempVerts = rawVerts.values().toArray();
				synchronized (currPolyGroup.rawVertOrder) {
					for (Integer currIndex : currPolyGroup.rawVertOrder) {
						if (currIndex < tempVerts.length && currIndex != -1) {
							//Trace.log((i++) + "  " + currIndex + "   " + ((Vertex) tempVerts[currIndex]).toString());
						} else {
							//Trace.log((i++) + "------------------------------------------");
						}
						vboIndex.put(currIndex);
					}
				}
				vboIndex.flip();
				surfaceVertexOrder.put(currSurface, vboIndex);
			}
		}
	}

	public void render() {
		if (refreshVerts || reUploadVerts || refreshIndices) {
			rebuildSurfaces();
			if (refreshVerts) {
				finalizeVerts();
				refreshVerts = false;
			}
			if (reUploadVerts) {
				uploadVerts();
				reUploadVerts = false;
			}
			if (refreshIndices) {
				finalizeIndices();
				refreshIndices = false;
			}
			finalize();
		}

		// int stride = (3 + 3 + 4) * 4; // 3 for Pos, 3 for normal, 4 for color, 4 for bytes
		glPushMatrix();
		glTranslatef(keyframes.here.pos.toGL().x, keyframes.here.pos.toGL().y, keyframes.here.pos.toGL().z);
		glRotatef(keyframes.here.rot.toGL().x, 1.0f, 0.0f, 0.0f);
		glRotatef(keyframes.here.rot.toGL().y, 0.0f, 1.0f, 0.0f);
		glRotatef(keyframes.here.rot.toGL().z, 0.0f, 0.0f, 1.0f);
		glScalef(keyframes.here.scale.toGL().x, keyframes.here.scale.toGL().y, keyframes.here.scale.toGL().z);

		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_NORMAL_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glEnable(GL_PRIMITIVE_RESTART);

		glPrimitiveRestartIndex(Integer.MAX_VALUE);

		synchronized (surfaceVertexOrder) {
			Iterator<Surface> itr = surfaceVertexOrder.keySet().iterator();
			while (itr.hasNext()) {
				Surface currSurface = itr.next();
				IntBuffer vboIndex = surfaceVertexOrder.get(currSurface);

				if (vboIndex != null) {
					// Trace.getCurrentMethodName(currSurface.name + "   " + vboIndex.capacity());

					currSurface.material.render();

					if (currSurface.useShaders) {
						currSurface.shaders.use();
					}

					int isSelectedUniform = ARBShaderObjects.glGetUniformLocationARB(currSurface.shaders.shader, "isSelected");
					ARBShaderObjects.glUniform1iARB(isSelectedUniform, 0);

					if (currSurface.textures.size() > 1) {
						int sampler01 = ARBShaderObjects.glGetUniformLocationARB(currSurface.shaders.shader, "sampler01");
						glActiveTexture(GL_TEXTURE0);
						glBindTexture(GL_TEXTURE_2D, currSurface.textures.get(0).getTextureID());
						ARBShaderObjects.glUniform1iARB(sampler01, 0);

						int sampler02 = ARBShaderObjects.glGetUniformLocationARB(currSurface.shaders.shader, "sampler02");
						glActiveTexture(GL_TEXTURE1);
						glBindTexture(GL_TEXTURE_2D, currSurface.textures.get(1).getTextureID());
						ARBShaderObjects.glUniform1iARB(sampler02, 1);
					}

					glBindBuffer(GL_ARRAY_BUFFER, vertexVBO_Index);
					int offset = 0;
					glVertexPointer(3, GL_FLOAT, strideBytes, offset);
					offset = 3 * 4;
					glNormalPointer(GL_FLOAT, strideBytes, offset);
					offset = (3 + 3) * 4;
					glColorPointer(4, GL_FLOAT, strideBytes, offset);
					offset = (3 + 3 + 4) * 4;
					glTexCoordPointer(2, GL_FLOAT, strideBytes, offset);

					glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

					glDrawElements(glMode, vboIndex);

					if (currSurface.useShaders) {
						currSurface.shaders.release();
					}
				}
			}
		}

		// surfaceVertexOrder.clear();

		glDisableClientState(GL_NORMAL_ARRAY);
		glDisableClientState(GL_VERTEX_ARRAY);

		glPopMatrix();
	}

	public synchronized int sizeRawVerts() {
		return rawVerts.size();
	}

	public synchronized void clearRawVerts() {
		rawVerts = new LinkedHashMap<String, Vertex>();
	}

	public synchronized boolean containsRawVert(Vertex searchMe) {
		return rawVerts.containsKey(searchMe.toString());
	}

	public synchronized void removeRawVert(Vertex removeMe) {
		rawVerts.remove(removeMe);
	}

	public synchronized void addRawVert(Vertex addMe) {
		rawVerts.put(addMe.toString(), addMe);
	}

	public synchronized Vertex getRawVert(String keyStr) {
		return rawVerts.get(keyStr);
	}

	public synchronized LinkedHashMap<String, Vertex> getRawVerts() {
		return rawVerts;
	}

	public synchronized void setRawVerts(LinkedHashMap<String, Vertex> rawVerts) {
		this.rawVerts = rawVerts;
	}
}
